
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "serial_linux.h"
#include "debug.h"

static SerialConfig g_serial_config = {9600,8,0,1,0,0};

static int serial_set_attr(int fd,SerialConfig *cfg)
{
#if 0	// autumn change for /dev/lte_cdev1, it come from vendor/nollec/lte-nollec-ril/nollec-ril.c

#define DATABITS       CS8
#define BAUD          B115200
#define STOPBITS        0
#define PARITYON        0
#define PARITY          0

	struct termios newtio; //place for old and new port settings for serial port

	if(fd<0||!cfg) return -1;

	memset(&newtio, 0, sizeof(newtio));

	fcntl(fd, F_SETFL, 0);
	newtio.c_cflag = BAUD | CRTSCTS | DATABITS | STOPBITS | PARITYON | PARITY | CLOCAL | CREAD;
	newtio.c_iflag = IGNPAR;
	newtio.c_oflag = 0;
	newtio.c_lflag =  ECHOE | ECHO | ICANON;        //ICANON;
	newtio.c_lflag =  0;                            //ICANON;

	newtio.c_cc[VMIN] = 1;
	newtio.c_cc[VTIME] = 0;
	newtio.c_cc[VERASE] = 0x8;
	newtio.c_cc[VEOL] = 0xD;

	newtio.c_cc[VINTR]    = 0;      /* Ctrl-c */
	newtio.c_cc[VQUIT]    = 0;      /* Ctrl-\ */
	newtio.c_cc[VERASE]   = 0;      /* del */
	newtio.c_cc[VKILL]    = 0;      /* @ */
	newtio.c_cc[VEOF]     = 4;      /* Ctrl-d */
	newtio.c_cc[VTIME]    = 0;      /* inter-character timer unused */
	newtio.c_cc[VMIN]     = 1;      /* blocking read until 1 character arrives */
	newtio.c_cc[VSWTC]    = 0;      /* '\0' */
	newtio.c_cc[VSTART]   = 0;      /* Ctrl-q */
	newtio.c_cc[VSTOP]    = 0;      /* Ctrl-s */
	newtio.c_cc[VSUSP]    = 0;      /* Ctrl-z */
	newtio.c_cc[VEOL]     = 0;      /* '\0' */
	newtio.c_cc[VREPRINT] = 0;      /* Ctrl-r */
	newtio.c_cc[VDISCARD] = 0;      /* Ctrl-u */
	newtio.c_cc[VWERASE]  = 0;      /* Ctrl-w */
	newtio.c_cc[VLNEXT]   = 0;      /* Ctrl-v */
	newtio.c_cc[VEOL2]    = 0;      /* '\0' */
	newtio.c_cc[VMIN] = 1;
	newtio.c_cc[VTIME] = 0;
	newtio.c_cc[VERASE] = 0x8;
	newtio.c_cc[VEOL] = 0xD;

	tcflush(fd, TCIFLUSH);
	tcsetattr(fd, TCSANOW, &newtio);
#else
	struct termios options;

	if(fd<0||!cfg) return -1;

	d_verbose(": serial fd [%d] set attribute (%d,%d,%d,%d,%d,%d).\n",
			fd,cfg->baudrate,cfg->databits,
			cfg->parity,cfg->stopbit,
			cfg->hw_flowctrl,cfg->sw_flowctrl);

	bzero(&options,sizeof(options));
	tcgetattr( fd, &options );

	cfmakeraw(&options);

	switch(cfg->baudrate){
		case 1200 :
			cfsetispeed( &options, B1200 );
			cfsetospeed( &options, B1200 );
			break;
		case 2400 :
			cfsetispeed( &options, B2400 );
			cfsetospeed( &options, B2400 );
			break;
		case 4800 :
			cfsetispeed( &options, B4800 );
			cfsetospeed( &options, B4800 );
			break;
		case 9600 :
			cfsetispeed( &options, B9600 );
			cfsetospeed( &options, B9600 );
			break;
		case 19200 :
			cfsetispeed( &options, B19200 );
			cfsetospeed( &options, B19200 );
			break;
		case 38400 :
			cfsetispeed( &options, B38400 );
			cfsetospeed( &options, B38400 );
			break;
		case 57600 :
			cfsetispeed( &options, B57600 );
			cfsetospeed( &options, B57600 );
			break;
		case 115200 :
			cfsetispeed( &options, B115200 );
			cfsetospeed( &options, B115200 );
			break;
		case 230400 :
			cfsetispeed( &options, B230400 );
			cfsetospeed( &options, B230400 );
			break;
		case 460800 :
			cfsetispeed( &options, B460800 );
			cfsetospeed( &options, B460800 );
			break;
		default:
			d_error(": invalid baud rate [%d]\n", cfg->baudrate );
			return -1;
	}

	switch(cfg->databits){
		case 5 :
			options.c_cflag &= ~CSIZE;
			options.c_cflag |= CS5;
			break;
		case 6 :
			options.c_cflag &= ~CSIZE;
			options.c_cflag |= CS6;
			break;
		case 7 :
			options.c_cflag &= ~CSIZE;
			options.c_cflag |= CS7;
			break;
		case 8 :
			options.c_cflag &= ~CSIZE;
			options.c_cflag |= CS8;
			break;
		default:
			d_error(": invalid databits [%d]\n", cfg->databits);
			return -1;
	}

	switch(cfg->parity){
		case 0 :		//NONE
			options.c_cflag &= ~PARENB;
			options.c_iflag &= ~INPCK;
			break;
		case 1 :		//ODD
			options.c_cflag |= PARENB;
			options.c_cflag |= PARODD;
			options.c_iflag |= ( INPCK | ISTRIP );
			break;
		case 2 :		//EVEN
			options.c_cflag |= PARENB;
			options.c_cflag &= ~PARODD;
			options.c_iflag |= ( INPCK | ISTRIP );
			break;
		default:
			d_error(": invalid parity [%d]\n", cfg->parity);
			return -1;
	}

	switch(cfg->stopbit){
		case 1 :
			options.c_cflag &= ~CSTOPB;
			break;
		case 2 :
			options.c_cflag |= CSTOPB;
			break;
		default:
			d_error(": invalid stopbits [%d]\n", cfg->stopbit);
			return -1;
	}

	switch(cfg->hw_flowctrl) {
		case 0 :		//OFF
			options.c_cflag &= ~CRTSCTS;
			break;
		case 1 :		//ON
			options.c_cflag |= CRTSCTS;
			break;
		default:
			d_error(": invalid hw_flowctrl [%d]\n", cfg->hw_flowctrl);
			return -1;
	}

	switch(cfg->sw_flowctrl){
		case 0 :		//OFF
			options.c_iflag &= ~( IXON | IXOFF | IXANY );
			break;
		case 1 :		//ON
			options.c_iflag |= ( IXON | IXOFF | IXANY );
			break;
		default:
			d_error(": invalid sw_flowctrl [%d]\n",cfg->sw_flowctrl);
			return -1;
	}

	//options.c_iflag = IGNCR | IGNBRK | IGNPAR;	// for 0x0a to 0x0d
	options.c_iflag = IGNBRK | IGNPAR;	// for 0x0a to 0x0d
	options.c_oflag = 0;
	options.c_lflag = 0;
	options.c_cc[VMIN] = 1;
	options.c_cc[VTIME] = 5;

	// TCSANOW
	if( tcsetattr( fd, TCSAFLUSH, &options ) == -1){
		d_error(": tcsetattr error!\n");
		return -1;
	}

	tcflush(fd, TCIOFLUSH);
#endif

	return 0;
}

int serial_init(unsigned char *fn)
{
	int fd=-1;
	int ret=0;
	struct flock flk;

	if(!fn) return -1;

	d_verbose("[%s].\n",fn);
	for(ret=1,fd=-1;ret<SERIAL_OPEN_RETRY_TIMES;ret++){
		fd=open((const char *)fn,O_RDWR|O_NOCTTY|O_NDELAY|O_SYNC);
		if(fd>0) break;
		d_error(": cannot open [%s], retry [%d]!\n",fn,ret);
		usleep(SERIAL_OPEN_RETRY_DELAY_TIME);
	}

	if(ret>=SERIAL_OPEN_RETRY_TIMES){
		d_error(": serail [%s] is locked!\n",fn);
		return -1;
	}

	flk.l_type=F_WRLCK|F_RDLCK;
        flk.l_whence=SEEK_SET;
        flk.l_start=0;
        flk.l_len=0;
        flk.l_pid=getpid();
	ret=fcntl(fd,F_SETLKW,&flk);
	if(ret<0){
		d_error(": fcntl error!\n");
		return -1;
	}

	ret=serial_set_attr(fd,&g_serial_config);
	if(ret < 0){
		d_error(": set [%s] attribute error!\n",fn);
	}

	return fd;
}

int serial_close(int fd)
{
	int ret=0;
	struct flock flk;
	d_verbose(": serial fd [%d] close.\n",fd);

	if(fd<0) return -1;

	flk.l_type=F_UNLCK;
        flk.l_whence=SEEK_SET;
        flk.l_start=0;
        flk.l_len=0;
        flk.l_pid=getpid();
	ret=fcntl(fd,F_SETLK,&flk);
	if(ret<0){
		d_error(": fcntl error!\n");
		return -1;
	}

	return close(fd);
}

int serial_write(int fd,const void *buf,size_t count)
{
	int n=0,r=count;

	d_verbose(": count = [%d]\n", count);
	d_debug_bin_buffer(buf,count,"write");

	while(count>0){
		n=write(fd,buf,count);
		if(!n){
			r=0;
			break;
		}
		if(n>0){
			count-=n;
			buf=(void*)((unsigned char*)buf+n);
			continue;
		}
		if(n<0){
			if(errno==EAGAIN||errno==EINTR)
				continue;
			r=-1;
			break;
		}
	}

	return r;
}

int serial_read(int fd,void *buf,size_t count,unsigned int cmd_tv_ms)
{
	fd_set read_fds;
	struct timeval tv;
	ssize_t len=0;
	int ret=0;
    int readlen;

	d_verbose(": cmd_tv_ms = [%d].\n", cmd_tv_ms );
    while(len < count){

    	FD_ZERO(&read_fds);
    	FD_SET(fd,&read_fds);

    	tv.tv_sec=(long)(cmd_tv_ms/1000);
    	tv.tv_usec=(long)(cmd_tv_ms%1000);

    	ret=select(fd+1,&read_fds,NULL,NULL,&tv);

    	if(!ret){
    		d_debug(": time is up.\n");
    		return 0;
    	}

    	if(ret<0){
            if(errno==EAGAIN||errno==EINTR)
				    continue;
    		d_error(": select serial error!\n");
    		return -1;
    	}

    	if(FD_ISSET(fd,&read_fds)){
    		readlen=read(fd,(unsigned char *)buf + len,count-len);
    		if(readlen<=0) {
                if(errno==EAGAIN||errno==EINTR)
				    continue;
    			d_error(": serial has been closed!\n");
    			return readlen;
    		}
            len+=readlen;
    	}
    }
	d_verbose(": len = [%d], buf = [%s].\n", len, buf );
	d_debug_bin_buffer(buf,len,"read");

	return (int)len;
}


int serial_read_once(int fd,void *buf,size_t count,unsigned int cmd_tv_ms)
{
	fd_set read_fds;
	struct timeval tv;
	ssize_t len=0;
	int ret=0;

	d_verbose(": cmd_tv_ms = [%d].\n", cmd_tv_ms );

	FD_ZERO(&read_fds);
	FD_SET(fd,&read_fds);

	tv.tv_sec=(long)(cmd_tv_ms/1000);
	tv.tv_usec=(long)(cmd_tv_ms%1000);

	ret=select(fd+1,&read_fds,NULL,NULL,&tv);

	if(!ret){
		d_debug(": time is up.\n");
		return 0;
	}

	if(ret<0){
		d_error(": select serial error!\n");
        if(errno==EAGAIN||errno==EINTR)
			return 0;
		return -1;
	}

	if(FD_ISSET(fd,&read_fds)){
		len=read(fd,(unsigned char *)buf + len,count-len);
		if(len<=0) {
			d_error(": serial has been closed!\n");
			return len;
		}
	}
	d_verbose(": len = [%d], buf = [%s].\n", len, buf );
	d_debug_bin_buffer(buf,len,"read");

	return (int)len;
}

